package allod.som

import allod.som.Lattice.Pattern
import allod.som.Lattice.NeuronLocation
import functions.step.Step
import functions.topological.TopologicalFunction
import input.InputSource

/**
 * Created by IntelliJ IDEA.
 * User: allod
 * Date: 3 січ 2010
 * Time: 18:55:26
 */

class Tuner(
        val tunableLattice: Lattice.Builder,
        val neighbourhoodFunction: TopologicalFunction,
        val step: Step,
        val data: InputSource) {
  def tune(iterations: Int) : Lattice = {
    for (iteration <- 0 until iterations)
      doIteration(iteration)
    tunableLattice.build()
  }

  def doIteration(iteration: Int) {
    val inputVector = data.randomElement
    val winnerIndex = competition(inputVector)
    adaptation(inputVector, winnerIndex, iteration)
  }

  def competition(input: Pattern) = {
    tunableLattice.closestNeuron(input)
  }

  def cooperation(winnerIndex: NeuronLocation, neighborIndex: NeuronLocation, iteration: Int) = {
    neighbourhoodFunction.topologicalNeighbourhood(winnerIndex, neighborIndex, iteration)
  }

  def adaptation(input: Pattern, winnerIndex: NeuronLocation, iteration: Int) {
    val winner = tunableLattice.neuronAt(winnerIndex)
    for{
      i <- 0 until tunableLattice.columns
      j <- 0 until tunableLattice.rows
      val neighborIndex = (i, j)
      if (neighborIndex != winnerIndex)
      val activeNeuron = tunableLattice.neuronAt(i, j)
    } {
      val coefficient = step(iteration) * cooperation(winnerIndex, neighborIndex, iteration)
      val deltaWeight = List.map2(input, activeNeuron)((x, y) => coefficient * (x - y))
      val newWeight = List.map2(tunableLattice.neuronAt(i, j), deltaWeight)(_ + _)
      tunableLattice.tuneNeuron(neighborIndex, newWeight)
    }
    val winnerCoefficient = step(iteration) * cooperation(winnerIndex, winnerIndex, iteration)
    val deltaWeight = List.map2(input, winner)((x, y) => winnerCoefficient * (x - y))
    val newWeight = List.map2(winner, deltaWeight)(_ + _)
    tunableLattice.tuneNeuron(winnerIndex, newWeight)
  }
}